/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.IO;
#if !WindowsCE && !PocketPC
using System.Runtime.Serialization.Formatters.Binary;
#endif
using System.Text;
using STSdb.General.Buffers;
using STSdb.General.Streams;

namespace STSdb.Files
{
    public class BinaryFile : IFile
    {
        public IFile File { get; private set; }
        public ulong Position { get; set; }
        
        public BinaryFile(IFile file)
        {
            File = file;
            Position = 0;
        }
        
        #region IFile Members

        public void Write(ulong position, byte[] buffer, int index, int count)
        {
            File.Write(position, buffer, index, count);
            Position = position + (ulong)count;
        }

        public void Read(ulong position, byte[] buffer, int index, int count)
        {
            File.Read(position, buffer, index, count);
            Position = position + (ulong)count;
        }

        public void Flush()
        {
            File.Flush();
        }

        public void Close()
        {
            File.Close();
        }

        public ulong Length
        {
            get { return File.Length; }
        }

        #endregion

        #region Readers

        public void Read(byte[] buffer, int index, int count)
        {
            Read(Position, buffer, index, count);
        }

        public byte ReadByte()
        {
            byte[] buffer = new byte[sizeof(Byte)];
            Read(buffer, 0, buffer.Length);

            return buffer[0];
        }

        public SByte ReadSByte()
        {
            byte[] buffer = new byte[sizeof(SByte)];
            Read(buffer, 0, buffer.Length);

            return (SByte)buffer[0];
        }

        public Boolean ReadBoolean()
        {
            byte[] buffer = new byte[sizeof(Boolean)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToBoolean(buffer, 0);
        }

        public Int16 ReadInt16()
        {
            byte[] buffer = new byte[sizeof(Int16)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToInt16(buffer, 0);
        }

        public Int32 ReadInt32()
        {
            byte[] buffer = new byte[sizeof(Int32)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToInt32(buffer, 0);
        }

        public Int64 ReadInt64()
        {
            byte[] buffer = new byte[sizeof(Int64)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToInt64(buffer, 0);
        }

        public UInt16 ReadUInt16()
        {
            byte[] buffer = new byte[sizeof(UInt16)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToUInt16(buffer, 0);
        }

        public UInt32 ReadUInt32()
        {
            byte[] buffer = new byte[sizeof(UInt32)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToUInt32(buffer, 0);
        }

        public UInt64 ReadUInt64()
        {
            byte[] buffer = new byte[sizeof(UInt64)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToUInt64(buffer, 0);
        }

        public Single ReadSingle()
        {
            byte[] buffer = new byte[sizeof(Single)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToSingle(buffer, 0);
        }

        public Double ReadDouble()
        {
            byte[] buffer = new byte[sizeof(Double)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToDouble(buffer, 0);
        }

        public Char ReadChar()
        {
            byte[] buffer = new byte[sizeof(Char)];
            Read(buffer, 0, buffer.Length);

            return BitConverter.ToChar(buffer, 0);
        }

        public Decimal ReadDecimal()
        {
            byte[] buffer = new byte[sizeof(Decimal)];
            Read(buffer, 0, buffer.Length);

            int[] bits = new int[sizeof(Decimal) / sizeof(int)];
            Buffer.BlockCopy(buffer, 0, bits, 0, buffer.Length);

            return new Decimal(bits);
        }

        public DateTime ReadDateTime()
        {
            return new DateTime(ReadInt64());
        }

        public string ReadString()
        {
            int length = ReadInt32();
            byte[] buffer = new byte[length];
            Read(buffer, 0, buffer.Length);

            return Encoding.Unicode.GetString(buffer);

            //long count = 0;
            //int maxLen = CountCompression.GetLength(int.MaxValue);
            //byte[] buffer = new byte[Math.Min(maxLen, (int)(Length - Position))];
            //Read(buffer, 0, buffer.Length);
            //int index = 0;
            //while (index < buffer.Length && CountCompression.IterateFromByte(ref count, buffer[index]))
            //    index++;

            //if (index >= buffer.Length)
            //    throw new InvalidDataException();

            //int oldLen = buffer.Length;
            //Array.Resize<byte>(ref buffer, index + (int)count);
            //Read(buffer, oldLen, buffer.Length - oldLen);

            //return Encoding.Unicode.GetString(buffer, index, (int)count);
        }

        public object ReadObject()
        {
            int size = ReadInt32();
            byte[] buffer = new byte[size];
            Read(buffer, 0, buffer.Length);
            using (MemoryStream ms = new MemoryStream(buffer))
            {
                BinaryReader reader = new BinaryReader(ms);
                return reader.ReadAsOjbect();
            }
        }

        public void ReadStream(Stream stream, ulong length)
        {
            byte[] buffer = new byte[StreamExtensions.BUFFER_SIZE];
            while (length > 0)
            {
                ulong count = Math.Min(length, (ulong)buffer.Length);
                Read(buffer, 0, (int)count);
                stream.Write(buffer, 0, (int)count);
                length -= (ulong)count;
            }
        }

        public Ptr ReadPtr()
        {
            byte[] buffer = new byte[Ptr.SIZE];
            Read(buffer, 0, buffer.Length);

            return StructurePrimitives.ByteArrayToStruct<Ptr>(buffer);
        }

        #endregion

        #region Writers

        public void Write(byte[] buffer, int index, int count)
        {
            Write(Position, buffer, index, count);
        }

        public void Write(Byte value)
        {
            Write(new byte[] { value }, 0, sizeof(Byte));
        }

        public void Write(SByte value)
        {
            Write(new byte[] { (byte)value }, 0, sizeof(SByte));
        }

        public void Write(Boolean value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(Boolean));
        }

        public void Write(Int16 value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(Int16));
        }

        public void Write(Int32 value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(Int32));
        }

        public void Write(Int64 value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(Int64));
        }

        public void Write(UInt16 value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(UInt16));
        }

        public void Write(UInt32 value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(UInt32));
        }

        public void Write(UInt64 value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(UInt64));
        }

        public void Write(Single value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(Single));
        }

        public void Write(Double value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(Double));
        }

        public void Write(Char value)
        {
            Write(BitConverter.GetBytes(value), 0, sizeof(Char));
        }

        public void Write(Decimal value)
        {
            int[] bits = Decimal.GetBits(value);
            byte[] buffer = new byte[sizeof(Decimal)];
            Buffer.BlockCopy(bits, 0, buffer, 0, buffer.Length);

            Write(buffer, 0, buffer.Length);
        }

        public void Write(DateTime value)
        {
            Write(value.Ticks);
        }

        public void Write(String value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            byte[] buffer = Encoding.Unicode.GetBytes(value);
            Write(buffer.Length);
            Write(buffer, 0, buffer.Length);
        }

        public void Write(object value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            using (MemoryStream ms = new MemoryStream())
            {
                BinaryWriter writer = new BinaryWriter(ms);
                writer.WriteAsOjbect(value);
                Write((int)ms.Length);
                Write(ms.GetBuffer(), 0, (int)ms.Length);
            }
        }

        public void Write(Stream stream)
        {
            byte[] buffer = new byte[StreamExtensions.BUFFER_SIZE];
            int count;
            stream.Seek(0, SeekOrigin.Begin);
            while ((count = stream.Read(buffer, 0, buffer.Length)) > 0)
                Write(buffer, 0, count);
        }

        internal void Write(Ptr value)
        {
            byte[] buffer = StructurePrimitives.StructToByteArray(value);

            Write(buffer, 0, buffer.Length);
        }

        #endregion

        #region Bit Operations

        public void SetBit(ulong bitIndex, bool value)
        {
            ulong position = bitIndex / 8;
            byte[] buffer = new byte[1];

            if (position < Length)
                Read(position, buffer, 0, 1);

            buffer.SetBit((int)(bitIndex % 8), value ? 1 : 0);
            Write(position, buffer, 0, 1);
        }

        public bool GetBit(ulong bitIndex)
        {
            ulong position = bitIndex / 8;
            byte[] buffer = new byte[1];
            Read(position, buffer, 0, 1);
            return buffer.GetBit((int)(bitIndex % 8)) == 0 ? false : true;
        }

        public bool FindFirstBit(ulong fromBitIndex, ulong bitCount, bool value, out ulong bitIndex)
        {
            if ((fromBitIndex + bitCount) / 8 > Length)
                throw new ArgumentException("fromBitIndex, bitCount");

            const ulong MAX_CHUNK = 8192 * 8; //8KB in bits
            byte[] buffer = new byte[MAX_CHUNK / 8];
            int val = value ? 1 : 0;
            ulong index = fromBitIndex;
            ulong count = bitCount;

            while (count > 0)
            {
                ulong chunk = Math.Min(MAX_CHUNK - index % 8, count);
                Read(index / 8, buffer, 0, (int)Math.Ceiling(chunk / 8.0));

                int idx = buffer.FindFirstBit((int)(index % 8), (int)chunk, val);
                if (idx >= 0)
                {
                    bitIndex = index + (ulong)idx - index % 8;
                    return true;
                }

                count -= chunk;
                index += chunk;
            }

            bitIndex = 0;
            return false;
        }

        public ulong GetBitCount(ulong fromBitIndex, ulong bitCount, bool value)
        {
            if ((fromBitIndex + bitCount) / 8 > Length)
                throw new ArgumentException("fromBitIndex, bitCount");

            const ulong MAX_CHUNK = 8192 * 8; //8KB in bits
            byte[] buffer = new byte[MAX_CHUNK / 8];
            int val = value ? 1 : 0;
            ulong index = fromBitIndex;
            ulong count = bitCount;
            ulong result = 0;

            while (count > 0)
            {
                ulong chunk = Math.Min(MAX_CHUNK - index % 8, count);
                Read(index / 8, buffer, 0, (int)Math.Ceiling(chunk / 8.0));

                result += (ulong)buffer.GetBitCount((int)(index % 8), (int)chunk, val);

                count -= chunk;
                index += chunk;
            }
            return result;
        }

        #endregion
    }
}
